﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//
using System.Diagnostics;
using System.Collections;
using System.IO;
using System.Net;

// MobiLib, Copyright Mirosław M. Mączka.  All Rights Reserved. This code released under the terms of the Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)

namespace MobiLibrary
{
    //http://en.wikipedia.org/wiki/PRC_(Palm_OS)#PRC_Header
    //http://wiki.mobileread.com/wiki/MOBI
    //
    // <-PRC_header-><-PalmDOC_header -><- MOBI_header -><- EXTH_header -><- MobiFullName ->
    //               <- Record0                                                               -><- Record1     -->
    // <- MetaBuffer                                                                          ->
    //
    public partial class MobiLib:IMobiHeader
    {
        //
        private static object olock = new object();
        //
        //----------------------------------------------------------------------------------------------------------------------------------
        public struct MemoryBlock
        {
            public int Offset { get; set; }
            public int Length { get { return Buffer.Length; } }
            public byte[] Buffer { get; set; }
            //
            public override string ToString()
            {
                return string.Format("Offset={0}, Length={1}", Offset, Length);
            }
        }
        //
        //==================================================================================================================================
        //
        /// <summary>
        /// Rozszerza blok pamięci EXTH
        /// jeżeli WithExceptions==true generuje InsufficientMemoryException("EXTH") przy niepowodzeniu
        /// wymaga exthHdr != null
        /// </summary>
        /// <param name="newSize">nowa wielkość EXTH w bajtach</param>
        /// <returns>false w przypadku niepowodzenia</returns>
        public bool ExpandEXTH(int newSize)
        {
            if (exthHdr.mb.Length >= newSize) return true;
            //
            if ((exthHdr.mb.Offset + +mfn.mb.Length + newSize + 2) > MetaBuffer.Length)
            {
                if(WithExceptions)throw new InsufficientMemoryException("EXTH");
                return false;
            }
            //
            //przesunąc FullName
            int diff = newSize - exthHdr.mb.Length;
            mobiHdr.FullNameOffset = mobiHdr.FullNameOffset + diff;
            mfn.mb.Offset = mfn.mb.Offset + diff;
            //
            //
            byte[] bb = new byte[newSize];
            Array.Copy(exthHdr.mb.Buffer, bb, exthHdr.mb.Length);
            exthHdr.mb.Buffer = bb;
            exthHdr.HeaderLength = bb.Length;
            //
            //
            return true;
        }
        //
        internal bool MoveBlocForwardk(int _base, int _range, int length)
        {
            Array.Copy(MetaBuffer.Buffer, _base, MetaBuffer.Buffer, _base + _range, length);
            return true;
        }
        //
        //==================================================================================================================================
        //
        public bool WithExceptions { get; set; }
        //
        public bool IsDirty { get { return prcHdr.IsDirty || pdocHdr.IsDirty || mobiHdr.IsDirty || mfn.IsDirty || (null==exthHdr)?false:exthHdr.IsDirty; } }
        internal MemoryBlock MetaBuffer;
        public MemoryBlock MB { get { return MetaBuffer; } }

        public string FileName{get;internal set;}
        //
        public PRC_header prcHdr;
        public PalmDOC_header pdocHdr;
        public MOBI_header mobiHdr;
        public MobiFullName mfn;
        public EXTH_header exthHdr;
        //
        //
        public bool ReadFile(string _fileName)
        {
            FileStream f = null;
            BinaryReader br = null;
            //
            prcHdr = null;
            pdocHdr = null;
            mobiHdr = null;
            mfn = null;
            exthHdr = null;
            //
            FileName = _fileName; 
            //
            try
            {
                //
                f = File.OpenRead(FileName);
                br = new BinaryReader(f);
                //
                f.Seek(0, SeekOrigin.Begin);
                //
                // pierwsze podejście, trzeba odczytać dane z RecordDataOffset0,1
                byte[] bb = new byte[78 + 8 + 8];
                f.Read(bb, 0, bb.Length);
                prcHdr = new PRC_header(bb);
                prcHdr.mb.Offset = 0;
                //
                bb = new byte[prcHdr.RecordDataOffset0];// wszystko aż do MOBI to PRC
                f.Seek(0, SeekOrigin.Begin);
                f.Read(bb, 0, bb.Length);
                prcHdr = new PRC_header(bb);
                prcHdr.mb.Offset = 0;
                //
                MetaBuffer.Offset = 0;
                MetaBuffer.Buffer = new byte[prcHdr.RecordDataOffset1];
                f.Seek(0, SeekOrigin.Begin);
                f.Read(MetaBuffer.Buffer, 0, MetaBuffer.Length);
                //Record0Header r0h = new Record0Header(MB.Buffer);
                DumpAsBin("MetaBuffer.bin", MetaBuffer.Buffer);

                //
                string mobi = Bytes2String(MetaBuffer.Buffer, prcHdr.RecordDataOffset0 + 16, 4);
                int mobiLength = Bytes2Int(MetaBuffer.Buffer, prcHdr.RecordDataOffset0 + 20);
                //
                //
                //
                byte[] pdocBin = new byte[16]; Array.Copy(MetaBuffer.Buffer, prcHdr.RecordDataOffset0, pdocBin, 0, 16);
                pdocHdr = new PalmDOC_header(pdocBin);
                pdocHdr.mb.Offset = prcHdr.RecordDataOffset0;
                DumpAsBin("PalmDOC.bin", pdocHdr.mb.Buffer);
                //
                byte[] mobiBin = new byte[mobiLength]; Array.Copy(MetaBuffer.Buffer, prcHdr.RecordDataOffset0 + 16, mobiBin, 0, mobiLength);
                //MemoryStream ms = new MemoryStream(Record0Body);
                mobiHdr = new MOBI_header(mobiBin);
                mobiHdr.mb.Offset = prcHdr.RecordDataOffset0 + 16;
                DumpAsBin("mobi.bin", mobiHdr.mb.Buffer);
                //
                // UWAGA: bajty zerowe po napisie nie są wliczane w jego długość, (i chyba można je olać)
                byte[] fullName = new byte[mobiHdr.FullNameLength];
                Array.Copy(MetaBuffer.Buffer, prcHdr.RecordDataOffset0 + mobiHdr.FullNameOffset, fullName, 0, mobiHdr.FullNameLength);
                mfn = new MobiFullName(fullName);
                mfn.mb.Offset = prcHdr.RecordDataOffset0 + mobiHdr.FullNameOffset;
                //
                if (mobiHdr.isExth)
                {
                    string exth = Bytes2String(MetaBuffer.Buffer, prcHdr.RecordDataOffset0 + mobiLength + 16, 4);
                    int exthLength = Bytes2Int(MetaBuffer.Buffer, prcHdr.RecordDataOffset0 + mobiLength + 4 + 16);
                    byte[] exthBin = new byte[exthLength]; Array.Copy(MetaBuffer.Buffer, prcHdr.RecordDataOffset0 + mobiLength + 16, exthBin, 0, exthLength);
                    exthHdr = new EXTH_header(exthBin);
                    exthHdr.mb.Offset = prcHdr.RecordDataOffset0 + mobiLength + 16;
                    DumpAsBin("exth.bin", exthBin);

                }
                //
            }
            catch
            {
                if (WithExceptions) throw;
                //
                return false;
            }
            finally
            {
                if(null!=f)f.Dispose();
            }
            //
            return true;
        }
        //
        //
        public bool WriteFile()
        {
            return _writeFile(false, "");
        }
        //
        public bool WriteFileAs(string _newFileName)
        {
            return _writeFile(true, _newFileName);
        }
        //
        private bool _writeFile(bool newFile, string _newFileName)
        {
            FileStream f = null;
            BinaryWriter bw = null;
            //
            try
            {
                if (prcHdr.IsDirty) prcHdr.Store(MetaBuffer);
                if (pdocHdr.IsDirty) pdocHdr.Store(MetaBuffer);//Read Only
                if (null != mfn && mfn.IsDirty) { mfn.Store(MetaBuffer); mobiHdr.FullNameLength = mfn.NewFileLength; mobiHdr.IsDirty = true; }//*****
                if (mobiHdr.IsDirty) mobiHdr.Store(MetaBuffer);
                if (null != exthHdr && exthHdr.IsDirty)
                {
                    DumpAsBin("exthOUT.bin", exthHdr.mb.Buffer);
                    exthHdr.Store(MetaBuffer);
                }
                //
                if (newFile)
                {
#if true
                    if ("" == Path.GetDirectoryName(_newFileName))
                    {
                        _newFileName = Path.Combine(Path.GetDirectoryName(FileName), _newFileName);
                    }
#endif
                    File.Copy(FileName, _newFileName, true);
                }
                else
                {
                    _newFileName = FileName;
                }
                f = File.OpenWrite(_newFileName);
                bw = new BinaryWriter(f);
                bw.Seek(0, SeekOrigin.Begin);
                bw.Write(MetaBuffer.Buffer);
            }
            catch
            {
                if (WithExceptions) throw;
                //
                return false;
            }
            finally
            {
                if (null != f) f.Dispose();
            }
            //
            return true;
        }
        //
        //
        //
        public override string ToString()
        {
            return string.Format("MobiLib: FileName=[{0}],MetaBuffer={1}\n", FileName, MetaBuffer.ToString());
        }
        //
        public string Dump()
        {
            return string.Format("MobiLib: FileName=[{0}],MetaBuffer={1}\n", FileName, MetaBuffer.ToString());
        }
        //
        //==================================================================================================================================
        //
        public void ShowMB()
        {
            Console.WriteLine("--[{0}]----------------------------------", FileName);
            Console.WriteLine("MetaBuffer: {0}", MetaBuffer.ToString());
            if (null != mfn) Console.WriteLine("FullFileName: {0}", mfn.mb.ToString());
            Console.WriteLine("MOBI_HDR: {0}", mobiHdr.mb.ToString());
            Console.WriteLine("OrtographicIndex: {0}", mobiHdr.OrtographicIndex);
            Console.WriteLine("InflectionIndex: {0}", mobiHdr.InflectionIndex);
            Console.WriteLine("FirstNonbookIndex: {0}", mobiHdr.FirstNonbookIndex);
            Console.WriteLine("FirstImageindex: {0}", mobiHdr.FirstImageindex);
            Console.WriteLine("HuffmanRecordOffset: {0}", mobiHdr.HuffmanRecordOffset);
            Console.WriteLine("HuffmanTableOffset: {0}", mobiHdr.HuffmanTableOffset);
            Console.WriteLine("DrmOffset: {0}", mobiHdr.DrmOffset);
            if (null != exthHdr) Console.WriteLine("EXTH_HDR: {0}", exthHdr.mb.ToString());
            Console.WriteLine(".");
        }
        //
        public void DumpAll()
        {
            Console.WriteLine(Dump());
            Console.WriteLine(prcHdr.Dump());
            Console.WriteLine(pdocHdr.Dump());
            Console.WriteLine(mobiHdr.Dump());
            if (null != exthHdr) Console.WriteLine(exthHdr.Dump());
            if(null != mfn)Console.WriteLine(mfn.Dump());
        }
        //
        public void ShowInfo()
        {
            Console.WriteLine("--[{0}]----------------------------------", FileName);
            Console.WriteLine("Name: [{0}]", prcHdr.Name);
            Console.WriteLine("CreationTime: {0}", prcHdr.dtCreationTime);
            Console.WriteLine("ModificationTime: {0}", prcHdr.dtModificationTime);
            Console.WriteLine("BackupTime: {0}", prcHdr.dtBackupTime);
            //Console.WriteLine("Creator: [{0}]", prcHdr.sCreator);
            //Console.WriteLine("Type: [{0}]", prcHdr.sType);

            Console.WriteLine("FullName: [{0}]", mfn.GetString());
            Console.WriteLine("TextEncoding: {0}", mobiHdr.sTextEncoding);
            Console.WriteLine("Locale: {0}", mobiHdr.sLocale);
            Console.WriteLine("InputLanguage: {0}", mobiHdr.sInputLanguage);
            Console.WriteLine("OutputLanguage: {0}", mobiHdr.sOutputLanguage);
            if (null != exthHdr)
            {
                Console.WriteLine("EXTHDR:");
                Console.WriteLine(exthHdr.ToString());
            }
            Console.WriteLine(".");
        }
        //
        //==================================================================================================================================
        //
    }//class
}//namespace
